{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备工作\n",
    "- 工具实现\n",
    "- 工具描述\n",
    "- 用户Query"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "# 工具的实现\n",
    "def get_current_temperature(location: str, unit: str = \"celsius\"):\n",
    "    \"\"\"获取某[location]地点的当前温度。\n",
    "\n",
    "    Args:\n",
    "        location: 需要获取温度的地点，格式为 \"City, State, Country\".\n",
    "        unit: 返回温度的单位。默认为 \"celsius摄氏度\"。(可选: [\"celsius\", \"fahrenheit\"])\n",
    "\n",
    "    Returns:\n",
    "        一个包含温度、地点和温度单位的字典。\n",
    "    \"\"\"\n",
    "    return {\n",
    "        \"temperature\": 26.1,\n",
    "        \"location\": location,\n",
    "        \"unit\": unit,\n",
    "    }\n",
    "\n",
    "\n",
    "def get_temperature_date(location: str, date: str, unit: str = \"celsius\"):\n",
    "    \"\"\"获取某[location]地点的某[data]的温度。\n",
    "\n",
    "    Args:\n",
    "        location: 需要获取温度的地点，格式为 \"City, State, Country\".\n",
    "        date: 想要获取哪一天的温度, 格式为 \"Year-Month-Day\".\n",
    "        unit: 返回温度的单位。默认为 \"celsius摄氏度\"。(可选: [\"celsius\", \"fahrenheit\"])\n",
    "\n",
    "    Returns:\n",
    "        一个包含温度、地点、日期和温度单位的字典。\n",
    "    \"\"\"\n",
    "    return {\n",
    "        \"temperature\": 25.9,\n",
    "        \"location\": location,\n",
    "        \"date\": date,\n",
    "        \"unit\": unit,\n",
    "    }\n",
    "\n",
    "# 基于函数名调用\n",
    "def get_function_by_name(name):\n",
    "    if name == \"get_current_temperature\":\n",
    "        return get_current_temperature\n",
    "    if name == \"get_temperature_date\":\n",
    "        return get_temperature_date\n",
    "\n",
    "# 工具描述\n",
    "TOOLS = [\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_current_temperature\",\n",
    "            \"description\": \"Get current temperature at a location.\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"location\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": 'The location to get the temperature for, in the format \"City, State, Country\".',\n",
    "                    },\n",
    "                    \"unit\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"enum\": [\"celsius\", \"fahrenheit\"],\n",
    "                        \"description\": 'The unit to return the temperature in. Defaults to \"celsius\".',\n",
    "                    },\n",
    "                },\n",
    "                \"required\": [\"location\"],\n",
    "            },\n",
    "        },\n",
    "    },\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_temperature_date\",\n",
    "            \"description\": \"Get temperature at a location and date.\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"location\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": 'The location to get the temperature for, in the format \"City, State, Country\".',\n",
    "                    },\n",
    "                    \"date\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": 'The date to get the temperature for, in the format \"Year-Month-Day\".',\n",
    "                    },\n",
    "                    \"unit\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"enum\": [\"celsius\", \"fahrenheit\"],\n",
    "                        \"description\": 'The unit to return the temperature in. Defaults to \"celsius\".',\n",
    "                    },\n",
    "                },\n",
    "                \"required\": [\"location\", \"date\"],\n",
    "            },\n",
    "        },\n",
    "    },\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "        \"name\": \"info_search_web\",\n",
    "        \"description\": \"Search web pages using search engine. Use for obtaining latest information or finding references.\",\n",
    "        \"parameters\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"query1\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"Search query in Google search style, using 3-5 keywords.\"\n",
    "                },\n",
    "                \"query2\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"Search query in Google search style, using 3-5 keywords. 尝试使用与前面不同的关键词获得更多的内容\"\n",
    "                },\n",
    "                \"query3\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"Search query in Google search style, using 3-5 keywords. 尝试后退一步思考，使用不同的查询内容\"\n",
    "                },\n",
    "                \"date_range\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"enum\": [\"all\", \"past_hour\", \"past_day\", \"past_week\", \"past_month\", \"past_year\"],\n",
    "                    \"description\": \"(Optional) Time range filter for search results.\"\n",
    "                }\n",
    "            },\n",
    "            \"required\": [\"query1\", \"query2\", \"query3\"]\n",
    "          }\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"message_ask_user\",\n",
    "            \"description\": \"向用户提问并等待回复。用于请求澄清、确认或收集更多信息。\",\n",
    "            \"parameters\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"text\": {\n",
    "                \"type\": \"string\",\n",
    "                \"description\": \"要呈现给用户的问题文本\"\n",
    "                },\n",
    "                \"attachments\": {\n",
    "                \"anyOf\": [\n",
    "                    {\"type\": \"string\"},\n",
    "                    {\"items\": {\"type\": \"string\"}, \"type\": \"array\"}\n",
    "                ],\n",
    "                \"description\": \"(可选) 与问题相关的文件或参考资料列表\"\n",
    "                },\n",
    "                \"suggest_user_takeover\": {\n",
    "                \"type\": \"string\",\n",
    "                \"enum\": [\"none\", \"browser\"],\n",
    "                \"description\": \"(可选) 建议用户接管的操作\"\n",
    "                }\n",
    "            },\n",
    "            \"required\": [\"text\"]\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "]\n",
    "\n",
    "# 用户Query\n",
    "MESSAGES = [\n",
    "    {\"role\": \"system\", \"content\": \"You are Qwen, created by Alibaba Cloud. You are a helpful assistant.Reply in Chinese.\\n\\nCurrent Date: 2025-03-05\"},\n",
    "    #{\"role\": \"system\", \"content\": \"You are Qwen, created by Alibaba Cloud. You are a helpful assistant.Reply in Chinese.\\n\\n如果你不知道今天的日期你需要主动询问用户\"},\n",
    "    {\"role\": \"user\",  \"content\": \"尚博信技术实力怎么样？\"},\n",
    "    {\"role\": \"user\",  \"assistant\": \"请问您可以提供更多有关尚博信的信息吗，例如它的主营业务领域或具体产品服务？这将有助于我为您提供更准确的答案。\"},\n",
    "    {\"role\": \"user\",  \"content\": \"主要是想了解尚博信在AI方面的实力\"},\n",
    "    #{\"role\": \"assistant\",  \"content\": \"为了提供准确的天气信息，我需要确认一下今天的日期。请告诉我今天的具体日期（格式为“年-月-日”），以便我可以查询今天的温度。明天的温度我会直接为您查询。\"},\n",
    "    #{\"role\": \"user\",  \"content\": \"2025-03-05\"},\n",
    "]\n",
    "\n",
    "\n",
    "# 以下是为了更美观的输出\n",
    "def format_response(response):\n",
    "    \"\"\"格式化响应内容为更易读的格式\"\"\"\n",
    "    formatted_response = {\n",
    "        \"model\": response.model,\n",
    "        \"created_at\": response.created_at,\n",
    "        \"done\": response.done,\n",
    "        \"done_reason\": response.done_reason,\n",
    "        \"total_duration\": response.total_duration,\n",
    "        \"load_duration\": response.load_duration,\n",
    "        \"_evalprompt_count\": response.prompt_eval_count,\n",
    "        \"prompt_eval_duration\": response.prompt_eval_duration,\n",
    "        \"eval_count\": response.eval_count,\n",
    "        \"eval_duration\": response.eval_duration,\n",
    "        \"message\": {\n",
    "            \"role\": response.message.role,\n",
    "            \"content\": response.message.content,\n",
    "            \"images\": response.message.images,\n",
    "            \"tool_calls\": [\n",
    "                {\n",
    "                    \"function\": {\n",
    "                        \"name\": call.function.name,\n",
    "                        \"arguments\": call.function.arguments\n",
    "                    }\n",
    "                } for call in response.message.tool_calls\n",
    "            ] if response.message.tool_calls else None\n",
    "        }\n",
    "    }\n",
    "    return json.dumps(formatted_response, indent=4, ensure_ascii=False)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## llm的工具调用\n",
    "\n",
    "有了之前的工具定义， 接下来就可以让大模型基于这些工具，自己来选择工具和执行工具"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "    \"model\": \"qwen2.5:14b\",\n",
      "    \"created_at\": \"2025-03-14T10:32:38.796118496Z\",\n",
      "    \"done\": true,\n",
      "    \"done_reason\": \"stop\",\n",
      "    \"total_duration\": 1698875132,\n",
      "    \"load_duration\": 31637038,\n",
      "    \"_evalprompt_count\": 620,\n",
      "    \"prompt_eval_duration\": 35000000,\n",
      "    \"eval_count\": 99,\n",
      "    \"eval_duration\": 1614000000,\n",
      "    \"message\": {\n",
      "        \"role\": \"assistant\",\n",
      "        \"content\": \"\",\n",
      "        \"images\": null,\n",
      "        \"tool_calls\": [\n",
      "            {\n",
      "                \"function\": {\n",
      "                    \"name\": \"message_ask_user\",\n",
      "                    \"arguments\": {\n",
      "                        \"text\": \"请问您可以提供更多有关尚博信的信息吗，例如它的主营业务领域或具体产品服务？这将有助于我为您提供更准确的答案。\"\n",
      "                    }\n",
      "                }\n",
      "            }\n",
      "        ]\n",
      "    }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "tools = TOOLS\n",
    "messages = MESSAGES[:]\n",
    "\n",
    "import ollama\n",
    "client = ollama.Client(host='http://192.168.20.43:11434')\n",
    "\n",
    "def llm_chat_tooluse(messages):\n",
    "    response = client.chat( # 仍然是用chat来调用\n",
    "        model='qwen2.5:14b', \n",
    "        tools=tools,    # 添加工具列表\n",
    "        messages=messages\n",
    "    )\n",
    "    return response\n",
    "\n",
    "#测试\n",
    "response = llm_chat_tooluse(messages)\n",
    "formatted_output = format_response(response)\n",
    "print(formatted_output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 调用工具，并将结果附加到历史对话\n",
    "\n",
    "如此，只需要对上一步生成的工具调用列表做处理。\n",
    "\n",
    "依次调用工具，并得出结果。\n",
    "\n",
    "再把结果作为role: toolname的形式 附在对话列表中，形成历史对话。\n",
    "\n",
    "大模型自然就可以结合整个历史对话，回答正确。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'NoneType' object is not callable",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[22], line 9\u001b[0m\n\u001b[0;32m      6\u001b[0m             fn_name: \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m=\u001b[39m fn_call[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n\u001b[0;32m      7\u001b[0m             fn_args: \u001b[38;5;28mdict\u001b[39m \u001b[38;5;241m=\u001b[39m fn_call[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124marguments\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n\u001b[1;32m----> 9\u001b[0m             fn_res: \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mdumps(\u001b[43mget_function_by_name\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfn_name\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mfn_args\u001b[49m\u001b[43m)\u001b[49m)  \u001b[38;5;66;03m#执行工具获得结果\u001b[39;00m\n\u001b[0;32m     11\u001b[0m             messages\u001b[38;5;241m.\u001b[39mappend({       \u001b[38;5;66;03m# 在上下文中添加工具调用的返回结果\u001b[39;00m\n\u001b[0;32m     12\u001b[0m                 \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrole\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtool\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m     13\u001b[0m                 \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m: fn_name,\n\u001b[0;32m     14\u001b[0m                 \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcontent\u001b[39m\u001b[38;5;124m\"\u001b[39m: fn_res,\n\u001b[0;32m     15\u001b[0m             })\n\u001b[0;32m     18\u001b[0m \u001b[38;5;66;03m# 测试\u001b[39;00m\n",
      "\u001b[1;31mTypeError\u001b[0m: 'NoneType' object is not callable"
     ]
    }
   ],
   "source": [
    "messages.append(response[\"message\"])            # 在上下文中加入工具调用的规划\n",
    "\n",
    "if tool_calls := messages[-1].get(\"tool_calls\", None):\n",
    "    for tool_call in tool_calls:                        # 遍历所有需要执行的工具\n",
    "        if fn_call := tool_call.get(\"function\"):\n",
    "            fn_name: str = fn_call[\"name\"]\n",
    "            fn_args: dict = fn_call[\"arguments\"]\n",
    "\n",
    "            fn_res: str = json.dumps(get_function_by_name(fn_name)(**fn_args))  #执行工具获得结果\n",
    "\n",
    "            messages.append({       # 在上下文中添加工具调用的返回结果\n",
    "                \"role\": \"tool\",\n",
    "                \"name\": fn_name,\n",
    "                \"content\": fn_res,\n",
    "            })\n",
    "\n",
    "\n",
    "# 测试\n",
    "import pprint\n",
    "pprint.pprint(messages)\n",
    "# print(messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第二次调用大模型，生成答案\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "    \"model\": \"qwen2.5:14b\",\n",
      "    \"created_at\": \"2025-03-05T08:23:23.911188902Z\",\n",
      "    \"done\": true,\n",
      "    \"done_reason\": \"stop\",\n",
      "    \"total_duration\": 1225532518,\n",
      "    \"load_duration\": 34268627,\n",
      "    \"_evalprompt_count\": 558,\n",
      "    \"prompt_eval_duration\": 162000000,\n",
      "    \"eval_count\": 41,\n",
      "    \"eval_duration\": 951000000,\n",
      "    \"message\": {\n",
      "        \"role\": \"assistant\",\n",
      "        \"content\": \"今天北京的温度是 26.1 摄氏度，明天预计会略微下降到 25.9 摄氏度。请注意适时增减衣物哦！\",\n",
      "        \"images\": null,\n",
      "        \"tool_calls\": null\n",
      "    }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "#此时的messages已经是包括了工具调用的结果的了\n",
    "response = llm_chat_tooluse(messages)\n",
    "formatted_output = format_response(response)\n",
    "print(formatted_output)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
